#coding:utf8
import os, hashlib, time, pickle, json, re
from datetime import datetime, timedelta, date
import logging, copy, base64
import math

from django.shortcuts import HttpResponse
from django.core.cache import cache as cacheClient
from Crypto.Cipher import AES


def parseFloat(string):
    try:
        return float(string)
    except:
        return 0

def explode(splitString, string):
    return string.split(splitString) if string else []

def implode(joinString, arr):
    return joinString.join(list(map(str, arr)))

def parseInt(string):
    if isinstance(string, bytes):
        string = string.decode('utf-8')

    try:
        return int(string)
    except:
        return 0

def parseJson(string, default={}):
    try:
        result = json.loads(string)
        if isinstance(result, dict) or isinstance(result, list):
            return result
        else:
            return copy.deepcopy(default)
    except:
        return copy.deepcopy(default)

def parseDateTime(dt, filterDefault=False):
    if isinstance(dt, str):
        if filterDefault and dt[0:10] == '2020-01-01':
            return ''
        return dt

    if isinstance(dt, datetime):
        if filterDefault and dt == datetime(2020,1,1):
            return ''
        return dt.strftime("%Y-%m-%d %H:%M:%S")
    return ''

def parseDate(dt, filterDefault=False):
    if isinstance(dt, str):
        if filterDefault and dt[0:10] == '2020-01-01':
            return ''
        return dt

    if isinstance(dt, datetime) or isinstance(dt,date):
        if filterDefault and (dt == date(2020,1,1) or dt == date(1970,1,1)):
            return ''
        return dt.strftime("%Y-%m-%d")
    return ''

#把分转成元
def parseRmb(price):
    return round(1.0*price/100.0, 2)

#把元转成分
def parseRmbCent(priceString):
    if isinstance(priceString, str):
        priceString = priceString.strip()
    return int(round(parseFloat(priceString) * 100, 0))

#把秒转成天
def parseSeconds(seconds):
    day = seconds // 86400
    if seconds%86400>0:
        day += 1
    return day

#把天转成秒
def parseDays(days):
    seconds = days * 86400
    return seconds

def parseString(s):
    if s is None:
        return ''
    if isinstance(s, bytes):
        return s.decode('utf-8')
    return str(s)

"""
获取缓存
"""
def getCache(cacheType, cacheKey):
    rkey = "cache:%s:%s"%(cacheType, hashlib.md5(cacheKey.encode('utf8')).hexdigest())
    return cacheClient.get(rkey)

"""
设置缓存
"""
def setCache(cacheType, cacheKey, data, cacheTime=60):
    rkey = "cache:%s:%s"%(cacheType, hashlib.md5(cacheKey.encode('utf8')).hexdigest())
    return cacheClient.set(rkey, data, cacheTime)

#删除缓存
def deleteCache():
    return cacheClient.delete('cache:*')

"""
cache装饰器
"""
def cache(cacheTime):
    def decorator(func):
        def wrapper(*args, **kw):
            """
            缓存
            """
            cacheKey = '%s||%s'%(pickle.dumps(args), pickle.dumps(kw))
            cacheType = "%s.%s" %(func.__module__, func.__name__)
            cacheData = getCache(cacheType, cacheKey)
            if cacheData is not None:
                return cacheData

            data = func(*args, **kw) 
            setCache(cacheType, cacheKey, data, cacheTime)
            return data

        return wrapper

    return decorator

"""
查询sql缓存
cacheTime 单位秒
"""
def querySqlCache(sql, dbName='stat', cacheTime=60):
    cacheType = 'db'
    cacheKey = "%s||%s"%(dbName, sql)

    cacheData = getCache(cacheType, cacheKey)
    if cacheData is not None:
        return cacheData

    from django.db import connections
    cursor = connections[dbName].cursor()
    cursor.execute(sql)
    data = cursor.fetchall()
    if cacheTime > 0:
        setCache(cacheType, cacheKey, data, cacheTime)
    return data

"""
查询sql缓存
cacheTime 单位秒
"""
def querySqlCacheOne(sql, dbName='stat', cacheTime=60):
    for r in querySqlCache(sql, dbName, cacheTime):
        return r
    return None

"""
按日期 单计/合计 生成excel
data = [{'data': [{'day': '2017-10-01'}], 'group_key_value': 1}, {}]
keys = ['day', 'num']
keyDesc = {'day': u'日期'}
groupValues = {'0': u'站外', '1': u'站内'}
"""
def getExcelByDay(data, keys, keyDesc, groupValues={}):
    import xlwt, io 

    workbook = xlwt.Workbook()
    sheet = workbook.add_sheet('sheet',cell_overwrite_ok=True)

    xIndex = 0
    if len(keys) > 0:
        for n, item in enumerate(data):
            start = n * len(keys)
            sheet.write_merge(xIndex, 0, start+1, start+len(keys), groupValues.get(item['group_key_value'], ''))

    xIndex += 1
    sheet.write(xIndex, 0, keyDesc.get('day', ''))
    for n, item in enumerate(data):
        for nn, key in enumerate(keys):
            start = n*len(keys) + nn + 1
            sheet.write(xIndex, start, keyDesc.get(key, ''))
    
    others = []
    for n, item in enumerate(data[1:]):
        _d = {}
        for r in item['data']:
            _d[r['day']] = r
        others.append(_d)

    xIndex += 1
    for item in data[0]['data']:
        _day = item['day']
        sheet.write(xIndex, 0, _day)
        for _n, _k in enumerate(keys):
            sheet.write(xIndex, _n+1, item[_k])

        for _on, oItem in enumerate(others):
            _start = len(keys) * (_on + 1)
            for _n, _k in enumerate(keys):
                sheet.write(xIndex, _start+_n+1, oItem.get(_day, {}).get(_k))

        xIndex += 1
    
    sf = io.BytesIO()
    workbook.save(sf) 
    contents = sf.getvalue()
    sf.close()

    return contents 

"""
一般报表
result = [{'k': 'k', 'kk': 'kk'},]
keys = ['kk', 'k']
keyDesc = {'k': u'单k', 'kk': u'双k'}
"""
def GetExcelXlsx(result, keys, keyDesc):
    import xlsxwriter, io

    sf = io.BytesIO()
    workbook = xlsxwriter.Workbook(sf, {'constant_memory': True})
    sheet = workbook.add_worksheet()

    xIndex = 0
    for nn, key in enumerate(keys):
        sheet.write(xIndex, nn, keyDesc.get(key, ''))

    for item in result:
        xIndex += 1
        for nn, key in enumerate(keys):
            sheet.write(xIndex, nn, item.get(key, ''))
    workbook.close()

    content = sf.getvalue()
    sf.close()
    return content

def getDays(begin, end):
    days = range(
        int(time.mktime(time.strptime(end, "%Y-%m-%d"))),
        int(time.mktime(time.strptime(begin, "%Y-%m-%d"))-3600*24),
        -(3600*24)
    )

    dates = []
    for _day in days:
        day = time.strftime("%Y-%m-%d", time.localtime(_day))
        dates.append(day)

    return dates

def unique(arr):
    a = {}
    for r in arr:
        a[r] = 1
    return list(a.keys())

#获取文件file-like object
def getFileStreamMD5(fobj):
    m = hashlib.md5()
    while True:
        data = fobj.read(4096)
        if not data:
            break
        m.update(data)

    return m.hexdigest()

def GetFileSuffix(name):
    suffix = ""
    index = name.rfind('.')
    if index > -1:
        suffix = name[index:]
    return suffix

def GetRedis():
    return cacheClient.client

def GetRegions():
    lf = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'conf/region.conf')
    with open(lf) as fp:
        return json.loads(fp.read())
    return []
#
def HttpResponseJson(result):
    return HttpResponse(json.dumps(result, ensure_ascii=False), 'application/json')

def ApiResponseJson(request, code=200, msg='', data=None):
    result = {
        'code': code, 'msg': msg, 'data': data
    }

    try:
        requestBody = parseString(request.body)
    except:
        requestBody = ""

    logInfo = {
        'method': request.method,
        'user_id': request.user.id,
        'url': "%s?%s"%(request.META.get('PATH_INFO', ''), request.META.get('QUERY_STRING', '')),
        'request_body': requestBody,
        'response_body': result
    }
    logger = logging.getLogger("api")
    logger.info(json.dumps(logInfo))

    return SetHeadCors(request, HttpResponseJson(result))

def getDefaultDaterange():
    day7 = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
    return "%s - %s"%(day7, datetime.now().strftime("%Y-%m-%d"))

#设置跨域
def SetHeadCors(request, response):
    response['Access-Control-Allow-Origin'] = request.META.get('HTTP_ORIGIN', '*')
    response['Vary'] = 'Origin'
    response['Access-Control-Allow-Methods'] = 'PUT,POST,GET,DELETE,OPTIONS'
    response['Access-Control-Allow-Headers'] = request.META.get('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', '*')

    return response

def GetPostJson(request):
    if request.method == 'POST':
        return parseJson(request.body)
    return {}

#解密密码
def DecryptPassword(password):
    from Crypto.Cipher import AES
    import base64
    from core import settings
    key = settings.decryptPass['key']
    ivi = settings.decryptPass['ivi']
    try:
        obj = AES.new(key.encode('utf-8'), AES.MODE_CBC, ivi.encode('utf-8'))
        msg = bytes.decode(obj.decrypt(base64.b64decode(password)))
        paddingLen = ord(msg[len(msg)-1])
        return msg[0:-paddingLen]
    except:
        return ""

def GetCellValue(sheet, row, col):
    """col 可以传数字，字符串‘AZ‘"""
    def colname_to_num(colname):
        if type(colname) is not str:
            return colname
        col = 0
        power = 1
        for i in range(len(colname)-1,-1,-1):
            ch = colname[i]
            col += (ord(ch)-ord('A')+1)*power
            power *= 26
        return col-1

    col = colname_to_num(col)
    if row >= sheet.nrows or col >= sheet.ncols:
        return None

    cell = sheet.cell(row, col)
    if cell.ctype == 1:
        return cell.value.strip()
    if cell.ctype == 2:  # 数字
        return re.sub('.0$', '', str(cell.value))
    return cell.value

#aes128 cbc加密，pkcs7填充
def AESEncrypt(key, ivi, content):
    
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, ivi.encode('utf-8'))
    bs = 16
    pad = lambda s: s + (bs - len(s) % bs) * chr(bs - len(s) % bs)
    # 处理明文
    content_padding = pad(content)
    # 加密
    encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8'))
    # 重新编码
    result = str(base64.b64encode(encrypt_bytes), encoding='utf-8')
    return result

def AESDecrypt(key, ivi, secret):
    try:
        obj = AES.new(key.encode('utf-8'), AES.MODE_CBC, ivi.encode('utf-8'))
        msg = bytes.decode(obj.decrypt(base64.b64decode(secret)))
        paddingLen = ord(msg[len(msg)-1])
        return msg[0:-paddingLen]
    except:
        return ""

def GetPostBatchValue(request, key):
    return list(set(map(lambda l: parseInt(l.strip()), explode('\n', request.POST.get(key, '').strip()))))

def ForEach(sleepTime=0, isOnce=False):
    """
    循环跑
    """
    def decorator(func):
        def wrapper(*args, **kwargs):
            while True:
                func(*args, **kwargs)
                if sleepTime > 0:
                    time.sleep(sleepTime)
                if isOnce:
                    break
                
        return wrapper
    return decorator

"""
f request.FILES.get('file')
"""
def UploadFile(name, f, userId=0):
    userId = str(userId)
    from ..settings import BASE_DIR, Host, STATIC_URL
    import os.path

    fileData = f.read()
    suffix = GetFileSuffix(name)

    m = hashlib.md5()
    m.update(fileData)
    md5Key = m.hexdigest()

    fileName = "%s%s"%(md5Key, suffix)

    filePath = os.path.join(BASE_DIR, 'dist', 'upload', userId)
    os.makedirs(filePath, exist_ok=True)

    with open(os.path.join(filePath, fileName), 'wb') as fp:
        fp.write(fileData)

    return True, "%s%s%s/%s/%s"%(Host, STATIC_URL, 'upload', userId, fileName)

def UploadChunk(name, f, uKey, chunkId, chunkTotal, userId=0):
    from ..settings import UPLOAD_FILE
    import os.path

    userId = str(userId)
    suffix = GetFileSuffix(name)
    fileName = "%s%s"%(chunkId, suffix)

    filePath = os.path.join(UPLOAD_FILE['path'], userId, uKey)
    os.makedirs(filePath, exist_ok=True)

    with open(os.path.join(filePath, fileName), 'wb') as fp:
        fp.write(f.read())

    if chunkId != chunkTotal:
        return True, ""
    
    #判断子模块上传ok
    for nn in range(chunkTotal):
        _fileName = "%s/%s%s"%(filePath, nn+1, suffix)

        for _ in range(20):
            if os.path.isfile(_fileName):
                break
            time.sleep(0.5)
        
        if not os.path.isfile(_fileName):
            return False, "上传失败"

    wfp = open(filePath+suffix, 'wb')
    wfileName = "%s%s"%(uKey, suffix)

    for nn in range(chunkTotal):
        _fileName = "%s/%s%s"%(filePath, nn+1, suffix)
        with open(_fileName, 'rb') as fp:
            wfp.write(fp.read())
        os.unlink(_fileName)

    return True, "%s/%s/%s"%(UPLOAD_FILE['url'], userId, wfileName)

def Page(pageNo, total):
    pageSize = 10
    page = {
        'pageNo': pageNo,
        'pageSize': pageSize,
        'start': (pageNo - 1) * pageSize,
        'end': pageNo * pageSize,
        'total': total,
        'pageNum': 0,
    }

    page['end'] = min(page['total'], page['end'])
    page['totalPageNum'] = int(math.ceil(1.0*page['total'] / pageSize))
    minPage = max(1, pageNo-4)
    page['pages'] = range(minPage, min(minPage+10, page['totalPageNum']+1))

    return page

def ConvertExcelNumberToColumn(num):
    """将数字转换为对应的Excel列的大写字母"""
    result = ''
    while num > 0:
        num, remainder = divmod(num - 1, 26)
        result = chr(65 + remainder) + result
    return result

def compressJavaScript(content, mode='minify'):
    """
    JavaScript压缩和美化工具
    mode: 'minify' - 压缩JS代码, 'beautify' - 美化JS代码
    """
    if not content or not content.strip():
        return content

    if mode == 'minify':
        return minifyJavaScript(content)
    elif mode == 'beautify':
        return beautifyJavaScript(content)
    else:
        return content

def minifyJavaScript(code):
    """压缩JavaScript代码"""
    if not code:
        return code

    # 移除单行注释 (// ...)
    code = re.sub(r'//.*?$', '', code, flags=re.MULTILINE)

    # 移除多行注释 (/* ... */)
    code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL)

    # 移除多余的空白字符
    code = re.sub(r'\s+', ' ', code)

    # 移除操作符和标点符号周围的空格
    code = re.sub(r'\s*([{}();,:])\s*', r'\1', code)
    code = re.sub(r'\s*([=+\-*/<>!&|])\s*', r'\1', code)

    return code.strip()

def beautifyJavaScript(code):
    """美化JavaScript代码"""
    if not code:
        return code

    formatted = ''
    indent_level = 0
    indent_str = '  '
    in_string = False
    string_char = ''
    i = 0

    while i < len(code):
        char = code[i]
        next_char = code[i + 1] if i + 1 < len(code) else ''

        # 处理字符串
        if (char == '"' or char == "'") and not in_string:
            in_string = True
            string_char = char
            formatted += char
        elif char == string_char and in_string and (i == 0 or code[i-1] != '\\'):
            in_string = False
            string_char = ''
            formatted += char
        elif in_string:
            formatted += char
        else:
            # 处理字符串外的格式化
            if char == '{':
                formatted += char + '\n'
                indent_level += 1
                formatted += indent_str * indent_level
            elif char == '}':
                formatted = formatted.rstrip()
                indent_level -= 1
                formatted += '\n' + indent_str * indent_level + char
                if next_char and next_char not in ';,':
                    formatted += '\n' + indent_str * indent_level
            elif char == ';':
                formatted += char
                if next_char != '}':
                    formatted += '\n' + indent_str * indent_level
            elif char == ',':
                formatted += char + ' '
            else:
                formatted += char

        i += 1

    return formatted

